/*
 * basic_http.cpp
 *
 *  Created on: 14-May-2011
 *      Author: tan-ce
 */

#include "basic_http.h"
#include <algorithm>
#include <iostream>
#include <sstream>
#include <uriparser/Uri.h>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>

using namespace std;
using namespace tcetools;

BasicHttp::tokens BasicHttp::tokenizer(char *text, char delim, int token_count) {
	tokens ret;

	ret.push_back(text);
	token_count--;

	for (int i = 0; text[i] && token_count != 0; i++) {
		if (text[i] == delim) {
			text[i] = 0;

			// Skip over extra delimiters
			while (text[i + 1] == delim) i++;

			// If not the end of string, push into list
			if (text[i + 1]) ret.push_back(&text[i + 1]);

			token_count--;
		}
	}

	return ret;
}

char *BasicHttp::trim_string(char *str) {
	char *newstr = str;
	while (newstr[0] == ' ') newstr++;

	int i;
	for (i = 0; newstr[i]; i++);

	i--;
	while (newstr[i] == ' ') {
		newstr[i] = 0;
		i--;
	}

	return newstr;
}

std::string BasicHttp::chars2hex(char *buf, size_t len) {
	size_t hex_len = (len * 2);
	char *hex = new char[hex_len + 1];

	for (int i = 0; i < len; i++) {
		sprintf(&hex[i * 2], "%02x", (unsigned char) buf[i]);
	}
	hex[hex_len] = '\0';

	string ret(hex, hex_len);

	delete [] hex;

	return ret;
}

void BasicHttp::rand_chars(char *buf, size_t len) {
	int fd = open("/dev/urandom", O_RDONLY);
	ssize_t readlen = 0;

	while (readlen < len) {
		ssize_t ret = read(fd, buf, len - readlen);
		if (ret < 1) cerr << "ERROR: Could not get random values!" << endl;
		readlen += ret;
	}

	close(fd);
}

////////////////////////////////////////////////////////////////////////////////

BasicHttpServer::BasicHttpServer(Socket *conn){
	this->conn = conn;

	server_fields["Connection"] = "close";
	set_content_type_html_utf8();

	must_free_data = false;
	digest_auth = false;
	data = 0;
	data_len = 0;

	raw_post_data = 0;
	raw_post_size = 0;
	post_is_www_form_encoded = false;
}

void BasicHttpDigestAuthInfo::session_data_cleanup() {
	time_t now;
	time(&now);

	for (auth_sessions_map::iterator I = auth_sessions.begin();
			I != auth_sessions.end(); ++I) {
		auth_session *as = (I->second);

		if (as == NULL) continue;
		else if (now - as->last_active > MAX_SESSION_AGE) {
			delete as;
			auth_sessions.erase(I);
		}
	}
}

void BasicHttpServer::send_auth(std::string opaque, bool stale) {
	// Create a new session
	char buf[32];
	rand_chars(buf, 32);

	if (opaque.length() == 0) opaque = chars2hex(buf, 16);

	if ((*auth_sessions)[opaque] == NULL) {
		(*auth_sessions)[opaque] = new auth_session;
	}

	auth_session &as = *((*auth_sessions)[opaque]);

	as.nonce = chars2hex(buf + 16, 16);

	as.opaque = opaque;
	as.last_count = 0;
	time(&(as.last_active));
	as.nonce_age = as.last_active;

	server_fields["WWW-Authenticate"] =
			string("Digest realm=\"") + auth_info->get_realm() +
			"\",qop=\"auth\",nonce=\"" + as.nonce + "\",opaque=\"" +
			opaque + "\"";

	if (stale) server_fields["WWW-Authenticate"] += ",stale=true";

	reply(STATUS_UNAUTHORIZED, "Authorization Required");
}

bool BasicHttpServer::check_authorization() {
	auth_info->session_data_cleanup();

	// See if the path requested is on the whitelist
	for (vector<string>::iterator I = paths_noauth.begin(); I != paths_noauth.end(); ++I) {
		if (I->compare(uri.substr(0, I->length())) == 0) {
			return false;
		}
	}

	if (request_fields["authorization"].length() == 0) {
		send_auth("");
		return true;
	} else {
		field_map auth_resp;
		int auth_data_start = request_fields["authorization"].find("Digest");

		if (auth_data_start == string::npos) {
			send_auth("");
			return true;
		}

		decode_complex_field(auth_resp, request_fields["authorization"].substr(
				auth_data_start + 6,
				request_fields["authorization"].length() - auth_data_start - 6)
			);

		if (auth_resp.size() == 0 || auth_resp["opaque"].length() == 0 || (*auth_sessions)[auth_resp["opaque"]] == NULL) {
			send_auth("");
			return true;
		}

		auth_session &as = *((*auth_sessions)[auth_resp["opaque"]]);

		string user_cred = auth_info->HA1[auth_resp["username"]];

		int ncount;
		stringstream ss;

		ss << std::hex << auth_resp["nc"];
		ss >> ncount;

		if (user_cred.length() == 0 || ncount <= as.last_count) {
			send_auth("");
			return true;
		}

		string HA2 = MD5(string(get_req_type() == REQ_GET ? "GET" : "POST") + ":" + auth_resp["uri"]).hexdigest();

		string response = MD5(
				user_cred + ":" + as.nonce + ":" + auth_resp["nc"] + ":" + auth_resp["cnonce"] + ":auth:" + HA2
		).hexdigest();

		if (response.compare(auth_resp["response"]) == 0) {
			as.last_count = ncount;

			time_t now;
			time(&now);

			// There are issues.... disabled for now
			/* if (now - as.nonce_age > MAX_NONCE_AGE) {
				as.last_nonce = as.nonce;
				as.nonce = rand_hex16();
				as.nonce_age = now;
				server_fields["Authentication-Info"] = string("nextnonce=\"") + as.nonce + "\"";
			} */

			time(&as.last_active);

			auth_user = auth_resp["username"];

			return false;

		} else {
			// Perhaps the browser is just using a stale nonce
			response = MD5(
					user_cred + ":" + as.last_nonce + ":" + auth_resp["nc"] + ":" + auth_resp["cnonce"] + ":auth:" + HA2
			).hexdigest();
			send_auth(as.opaque, (response.compare(auth_resp["response"]) == 0));
			return true;
		}

	}
}

void BasicHttpServer::process_request() {
	if (!conn) {
		cerr << "BasicHttpServer: Hey, the socket is no longer valid. "
				  << "Hello segfault." << endl;
	}

	string line;
	char *uribuf;

	// Read and parse the main request line
	line = conn->read_line();
	char *buf = copy_string(line);
	tokens req_line = tokenizer(buf);

	if (req_line.size() != 3) {
		reply(STATUS_BAD_REQUEST, "Malformed request (Request Line)");
		return;
	}

	// Check HTTP version
	if (string(req_line[2]).compare("HTTP/1.1") != 0) {
		reply(STATUS_HTTP_VERSION_NOT_SUPPORTED, "HTTP version not supported");
		return;
	}

	// Read request type
	string req(req_line[0]);
	if (req.compare("GET") == 0) req_type = REQ_GET;
	else if (req.compare("POST") == 0) req_type = REQ_POST;
	else {
		reply(STATUS_BAD_REQUEST, "Bad request type");
		return;
	}

	// Process URI
	uribuf = new char[string(req_line[1]).length() + 1];
	uriUriStringToUnixFilenameA(req_line[1], uribuf);
	uri = uribuf;
	delete [] uribuf;

	delete [] buf;

	// Parse the rest of the fields
	while(1) {
		line = conn->read_line();

		if (line.length() == 0) break;

		char *buf = copy_string(line.c_str());
		tokens field = tokenizer(buf, ':', 2);

		if (field.size() != 2) {
			reply(STATUS_BAD_REQUEST, "Malformed request (fields)");
			return;
		}

		std::string key = trim_string(field[0]);
		std::transform(key.begin(), key.end(), key.begin(), ::tolower);

		request_fields[key] = trim_string(field[1]);

		delete [] buf;
	}

	// check_authorization() returns true on failure
	if (digest_auth && check_authorization()) return;

	if (req_type == REQ_POST) {
		// Get size of the POST data
		if (request_fields["content-length"].length() == 0) {
			reply(STATUS_BAD_REQUEST, "Malformed request (POST data length not specified)");
			return;
		}

		raw_post_size = atoi(request_fields["content-length"].c_str());

		if (raw_post_size > MAX_POST) {
			reply(STATUS_BAD_REQUEST, "Bad request (POST data too long)");
			return;
		}

		// Allocate the buffer then read it
		raw_post_data = new char[raw_post_size + 1];
		conn->blocking_read(raw_post_data, raw_post_size);
		raw_post_data[raw_post_size] = '\0';

		if (request_fields["content-type"].compare("application/x-www-form-urlencoded")
				== 0) { // Decode the form data

			// Make a copy -- tokenization is destructive
			char *pdata = new char[raw_post_size + 1];
			for (int i = 0; i < raw_post_size + 1; i++) pdata[i] = raw_post_data[i];

			post_is_www_form_encoded = true;
			tokens fields = tokenizer(pdata, '&');

			for (tokens_iter fentry = fields.begin(); fentry != fields.end(); ++fentry) {
				char *key = *fentry;

				// Find the equals sign
				size_t equals;
				for (equals = 0; key[equals] != '='; equals++) {
					if (key[equals] == '\0') {
						reply(STATUS_BAD_REQUEST, "Bad request (Invalid application/x-www-form-urlencoded)");
						return;
					}
				}

				key[equals] = '\0';
				char *value = &key[equals + 1];

				// From here key and value are valid
				uriUnescapeInPlaceExA(key, true, URI_BR_TO_UNIX);
				uriUnescapeInPlaceExA(value, true, URI_BR_TO_UNIX);
				post_data[string(key)] = string(value);
			}

			delete [] pdata;
		}
	}
}

void BasicHttpServer::begin_chunked_transfer() {
	if (!conn) {
		cerr << "BasicHttpServer: Hey, the socket is no longer valid. "
				  << "Hello segfault." << endl;
	}

	ostringstream reply_header;

	reply_header << "HTTP/1.1 200 OK\r\n";
	for (field_map_iter i = server_fields.begin();
			i != server_fields.end(); ++i) {
		reply_header << i->first << ": " << i->second << "\r\n";
	}
	reply_header << "Transfer-Encoding: chunked\r\n\r\n";

	string header = reply_header.str();

	conn->blocking_write(header.c_str(), header.length());
}

void BasicHttpServer::send_chunk(const char *buf, size_t len) {
	if (len == 0) {
		conn->blocking_write("0\r\n\r\n", 5);
		conn->close_sock();
		conn = 0;
		return;
	}

	ostringstream chunk;
	chunk << std::hex << len << "\r\n";
	conn->blocking_write(chunk.str().c_str(), chunk.str().length());
	conn->blocking_write(buf, len);
	conn->blocking_write("\r\n", 2);
}

void BasicHttpServer::reply(StatusCode reply_code, string reply_msg) {
	if (!conn) {
		cerr << "BasicHttpServer: Hey, the socket is no longer valid. "
				  << "Hello segfault." << endl;
	}

	ostringstream reply_header;

	reply_header << "HTTP/1.1 " << reply_code << " " << reply_msg << "\r\n";
	for (field_map_iter i = server_fields.begin();
			i != server_fields.end(); ++i) {
		reply_header << i->first << ": " << i->second << "\r\n";
	}

	if (data_len) reply_header << "Content-Length: " << data_len << "\r\n";

	reply_header << "\r\n";

	string header = reply_header.str();

	conn->blocking_write(header.c_str(), header.length());

	if (data) conn->blocking_write(data, data_len);

	conn->close_sock();
	conn = 0;
}

void BasicHttpServer::set_data(string str) {
	if (must_free_data) delete [] data;

	const char *buf = str.c_str();

	for (data_len = 0; buf[data_len]; data_len++);
	data = new char[data_len];
	must_free_data = true;
	for (ssize_t i = 0; i < data_len; i++) data[i] = buf[i];
}

void BasicHttpServer::decode_complex_field(field_map &ret, std::string todecode) {
	int length = todecode.length();
	int last_pos = 0;

	while (last_pos < length) {
		if (todecode[last_pos] == ' ' || todecode[last_pos] == ',') {
			last_pos++;
			continue;
		}

		int eqpos = todecode.find('=', last_pos);

		if (eqpos == string::npos) return;

		string fname = todecode.substr(last_pos, eqpos - last_pos);

		last_pos = eqpos + 1;

		if (last_pos >= length) return;

		if (todecode[last_pos] == '"') {
			last_pos++;
			int closingqt = todecode.find('"', last_pos);

			if (closingqt == string::npos) return;

			ret[fname] = todecode.substr(last_pos, closingqt - last_pos);
			last_pos = closingqt + 1;

			if (last_pos >= length) return;

		} else {
			int commapos = todecode.find(',', last_pos);

			if (commapos == string::npos) commapos = length;

			ret[fname] = todecode.substr(last_pos, commapos - last_pos);
			last_pos = commapos + 1;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
